package com.chat.common.chat.service.impl;



import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import com.chat.common.chat.dao.*;
import com.chat.common.chat.domain.dto.RoomBaseInfo;
import com.chat.common.chat.domain.entity.*;
import com.chat.common.chat.domain.enums.GroupRoleAPPEnum;
import com.chat.common.chat.domain.enums.GroupRoleEnum;
import com.chat.common.chat.domain.enums.HotFlagEnum;
import com.chat.common.chat.domain.enums.RoomTypeEnum;
import com.chat.common.chat.domain.vo.request.ChatMessageMemberReq;
import com.chat.common.chat.domain.vo.request.GroupAddReq;
import com.chat.common.chat.domain.vo.request.member.MemberAddReq;
import com.chat.common.chat.domain.vo.request.member.MemberDelReq;
import com.chat.common.chat.domain.vo.request.member.MemberReq;
import com.chat.common.chat.domain.vo.response.ChatMemberListResp;
import com.chat.common.chat.domain.vo.response.ChatRoomResp;
import com.chat.common.chat.domain.vo.response.MemberResp;
import com.chat.common.chat.service.ChatService;
import com.chat.common.chat.service.IRoomService;
import com.chat.common.chat.service.RoomAppService;
import com.chat.common.chat.service.adapter.ChatAdapter;
import com.chat.common.chat.service.adapter.MemberAdapter;
import com.chat.common.chat.service.adapter.RoomAdapter;
import com.chat.common.chat.service.cache.*;
import com.chat.common.chat.service.exception.GroupErrorEnum;
import com.chat.common.chat.service.strategy.msg.AbstractMsgHandler;
import com.chat.common.chat.service.strategy.msg.MsgHandlerFactory;
import com.chat.common.common.domain.vo.req.CursorPageBaseReq;
import com.chat.common.common.domain.vo.resp.CursorPageBaseResp;
import com.chat.common.common.event.GroupMemberAddEvent;
import com.chat.common.common.utils.AssertUtil;
import com.chat.common.user.dao.UserDao;
import com.chat.common.user.domain.entity.User;
import com.chat.common.user.domain.enums.RoleEnum;
import com.chat.common.user.service.IRoleService;
import com.chat.common.user.service.cache.UserCache;
import com.chat.common.user.service.cache.UserInfoCache;
import com.chat.common.user.service.impl.PushService;
import com.chat.common.websocket.domain.enums.WSBaseResp;
import com.chat.common.websocket.domain.vo.resp.ws.ChatMemberResp;
import com.chat.common.websocket.domain.vo.resp.ws.WSMemberChange;
import com.chat.common.websocket.service.adapter.WebSocketAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Description:
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-07-22
 */
@Service
public class RoomAppServiceImpl implements RoomAppService {

//    @Resource
//    private RoomFriendDao roomFriendDao;
//    @Resource
//    private RoomDao roomDao;
    @Resource
    private ContactDao contactDao;
    @Resource
    private HotRoomCache hotRoomCache;
    @Resource
    private MessageDao messageDao;
    @Resource
    private UserInfoCache userInfoCache;
    @Resource
    private RoomCaChe roomCache;
    @Resource
    private RoomGroupCache roomGroupCache;
    @Resource
    private RoomFriendCache roomFriendCache;
    @Resource
    private IRoomService iRoomService;
    @Resource
    private UserCache userCache;
    @Resource
    private GroupMemberDao groupMemberDao;
    @Resource
    private UserDao userDao;
    @Resource
    private ChatService chatService;
    @Resource
    private GroupMemberCache groupMemberCache;
    @Resource
    private IRoleService roleService;
    @Resource
    private PushService pushService;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private IRoomService roomService;


    /**
     * 获取会话列表
     * @param request
     * @param uid
     * @return
     */
    @Override
    public CursorPageBaseResp<ChatRoomResp> getContactPage(CursorPageBaseReq request, Long uid) {
        CursorPageBaseResp<Long> page = null;
        //一个是和用户的对话 还有一个是热门群聊的会话
        //登录的情况
        if(Objects.nonNull(uid)) {
            //游标
            Double hotEnd = getCursorOrNull(request.getCursor());
            Double hotStart = null;
            //基础的用户会话列表
            CursorPageBaseResp<Contact> contactPage = contactDao.getContactPage(request, uid);
            //遍历 房间list
            List<Long> roomList = contactPage.getList().stream().map(Contact::getRoomId).collect(Collectors.toList());
            if (!contactPage.getIsLast()) {
                hotStart = getCursorOrNull(contactPage.getCursor()) ;
            }
            //热门群聊的获取
            Set<ZSetOperations.TypedTuple<String>> roomRange = hotRoomCache.getRoomRange(hotStart, hotEnd);
            List<Long> hotRoomIds = roomRange.stream().map(ZSetOperations.TypedTuple::getValue).filter(Objects::nonNull).map(Long::parseLong).collect(Collectors.toList());
            roomList.addAll(hotRoomIds);
            page = CursorPageBaseResp.init(contactPage,roomList);
        }else {
            //未登录的情况 只显示热门群聊
            CursorPageBaseResp<Pair<Long, Double>> roomCursorPage = hotRoomCache.getRoomCursorPage(request);
            List<Long> hotRoomIds = roomCursorPage.getList().stream().map(Pair::getKey).collect(Collectors.toList());
            page = CursorPageBaseResp.init(roomCursorPage, hotRoomIds);
        }
        if (CollectionUtil.isEmpty(page.getList())) {
            return CursorPageBaseResp.empty();
        }
        // 最后组装会话信息（名称，头像，未读数等）
        List<ChatRoomResp> result = buildContactResp(uid, page.getList());
        return CursorPageBaseResp.init(page, result);
    }

    @Override
    public ChatRoomResp getContactDetail(Long uid, Long roomId) {
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(room,"房间号有误不存在");
        return buildContactResp(uid, Collections.singletonList(roomId)).get(0);
    }

    @Override
    public ChatRoomResp getContactDetailByFriend(Long uid, Long friendUid) {
        RoomFriend friendRoom = iRoomService.getFriendRoom(uid, friendUid);
        AssertUtil.isNotEmpty(friendRoom, "他不是您的好友");
        return buildContactResp(uid, Collections.singletonList(friendRoom.getRoomId())).get(0);
    }

    //群组详情
    @Override
    public MemberResp getGroupDetail(Long uid, long roomId) {
        RoomGroup roomGroup = roomGroupCache.get(roomId);
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(room,"roomId有误");
        Long onlineNum;
        if (isHotGroup(room)) {
            //热点群在redis 拉取 消息
            onlineNum = userCache.getOnlineNum();
        }else {
            //获取一个群组下面的所有用户id
            List<Long> memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
            //通过用户的id 去查询用户的在线状态
            onlineNum = userDao.getOnlineCount(memberUidList).longValue();
        }
        //获取用户的权限 是群主还是普通用户
        GroupRoleAPPEnum groupRoleAPPEnum = getGroupRole(uid, roomGroup, room);
        return MemberResp.builder()
                .role(groupRoleAPPEnum.getType())
                .roomId(roomId)
                .avatar(roomGroup.getAvatar())
                .onlineNum(onlineNum)
                .groupName(roomGroup.getName())
                .build();
    }


    @Override
    public CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room,"房间号有误,房间不存在");
        List<Long> memberUidList;
        if (isHotGroup(room)) {
            //热点群 全部展示
            memberUidList=null;
        } else {
          //只展示群内成员,先查询群信息 头像名字
            RoomGroup roomGroup = roomGroupCache.get(room.getId());
            memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
        }
        return chatService.getMemberPage(memberUidList, request);
    }

    @Override
    @Cacheable(cacheNames = "member", key = "'memberList.'+#request.roomId")
    public List<ChatMemberListResp> getMemberList(ChatMessageMemberReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room,"房间号有误");
        if (isHotGroup(room)) {
            //如果是热点群聊 只显示前100的用户
            List<User> memberList = userDao.getMemberList();
            return MemberAdapter.buildMemberList(memberList);
        } else {
            RoomGroup roomGroup = roomGroupCache.get(room.getId());
            List<Long> memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId());
            Map<Long, User> batch = userInfoCache.getBatch(memberUidList);
            return MemberAdapter.buildMemberList(batch);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delMember(Long uid, MemberDelReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room,"房间号有误");
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, "房间号有误");
        //判断用户是否在群组中
        GroupMember self = groupMemberDao.getMember(roomGroup.getId(), uid);
        AssertUtil.isNotEmpty(self, GroupErrorEnum.USER_NOT_IN_GROUP);
        //只有管理员或者是群主才能操作
        Long removeId=request.getUid();
        //判断是不是群主 群主是不能被移出的
        AssertUtil.isFalse(groupMemberDao.isLord(removeId,roomGroup.getId()),GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        // 1.2 管理员 判断是否是群主操作
        //先判断removeId是不是管理员
        if (groupMemberDao.isManager(removeId,roomGroup.getId())){
            //判断操作的用户uid 是否是群主
            boolean lord = groupMemberDao.isLord(uid, roomGroup.getId());
            AssertUtil.isFalse(lord,GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        }
        // 1.3 普通成员 判断是否有权限操作
        AssertUtil.isTrue(hasPower(self),GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        //判断用户还是否在群中
        GroupMember member = groupMemberDao.getMember(roomGroup.getId(), removeId);
        AssertUtil.isNotEmpty(member,GroupErrorEnum.USER_NOT_IN_GROUP);
        //移出群聊
        groupMemberDao.removeById(member.getId());
        // 发送移除事件告知群成员
        //查询全部成员
        List<Long> memberUidList = groupMemberCache.getMemberUidList(roomGroup.getRoomId());
        WSBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), member.getUid());
        //推送
        pushService.sendPushMsg(ws, memberUidList);
        //清空缓存
        groupMemberCache.evictMemberUidList(room.getId());
    }

    @Override
    @Transactional
    public Long addGroup(Long uid, GroupAddReq request) {
        RoomGroup roomGroup = roomService.createGroupRoom(uid);
        // 批量保存群成员
        List<GroupMember> groupMembers = RoomAdapter.buildGroupMemberBatch(request.getUidList(), roomGroup.getId());
        groupMemberDao.saveBatch(groupMembers);
        // 发送邀请加群消息==》触发每个人的会话
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, groupMembers,roomGroup , uid));
        return roomGroup.getRoomId();
    }

    //邀请好友
    @Override
    public void addMember(Long uid, MemberAddReq request) {
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room,"房间号有误");
        AssertUtil.isFalse(isHotGroup(room),"根群聊无需邀请");
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup,"房间号有误");
        GroupMember member = groupMemberDao.getMember(roomGroup.getId(), uid);
        AssertUtil.isNotEmpty(member,"您不是群成员,不可操作");
        //查询被邀请的用户是否在群聊中
        Set<Long> memberBatch = groupMemberDao.getMemberBatch(roomGroup.getId(), request.getUidList());
        List<Long> waitAddUidList = request.getUidList().stream().filter(a -> !memberBatch.contains(a)).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(waitAddUidList)) {
            return;
        }
        List<GroupMember> groupMembers= MemberAdapter.buildMemberAdd(waitAddUidList,roomGroup.getId());
        groupMemberDao.saveBatch(groupMembers);
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this,groupMembers,roomGroup,uid));
    }

    private boolean hasPower(GroupMember self) {
        return Objects.equals(self.getRole(), GroupRoleEnum.LEADER.getType())
                || Objects.equals(self.getRole(), GroupRoleEnum.MANAGER.getType())
                || roleService.hasPower(self.getUid(), RoleEnum.ADMIN);
    }

    private GroupRoleAPPEnum getGroupRole(Long uid, RoomGroup roomGroup, Room room) {
        GroupMember groupMember = Objects.isNull(uid) ? null : groupMemberDao.getMember(roomGroup.getId(), uid);
        if (Objects.nonNull(groupMember)) {
           return GroupRoleAPPEnum.of(groupMember.getRole());
        }else if(isHotGroup(room)){
            return GroupRoleAPPEnum.MEMBER;
        }else {
            return GroupRoleAPPEnum.REMOVE;
        }
    }

    private boolean isHotGroup(Room room) {
        return HotFlagEnum.YES.getType().equals(room.getHotFlag());
    }


    @NotNull
    private List<ChatRoomResp> buildContactResp(Long uid, List<Long> roomIds) {
        // 表情和头像
        Map<Long, RoomBaseInfo> roomBaseInfoMap = getRoomBaseInfoMap(roomIds, uid);
        // 最后一条消息
        List<Long> msgIds = roomBaseInfoMap.values().stream().map(RoomBaseInfo::getLastMsgId).collect(Collectors.toList());
        List<Message> messages = CollectionUtil.isEmpty(msgIds) ? new ArrayList<>() : messageDao.listByIds(msgIds);
        Map<Long, Message> msgMap = messages.stream().collect(Collectors.toMap(Message::getId, Function.identity()));
        //消息发送者的userinfo
        Map<Long, User> lastMsgUidMap = userInfoCache.getBatch(messages.stream().map(Message::getFromUid).collect(Collectors.toList()));
        // 消息未读数
        Map<Long, Integer> unReadCountMap = getUnReadCountMap(uid, roomIds);
        return roomBaseInfoMap.values().stream().map(room -> {
                    ChatRoomResp resp = new ChatRoomResp();
                    RoomBaseInfo roomBaseInfo = roomBaseInfoMap.get(room.getRoomId());
                    resp.setAvatar(roomBaseInfo.getAvatar());
                    resp.setRoomId(room.getRoomId());
                    resp.setActiveTime(room.getActiveTime());
                    resp.setHot_Flag(roomBaseInfo.getHotFlag());
                    resp.setType(roomBaseInfo.getType());
                    resp.setName(roomBaseInfo.getName());
                    Message message = msgMap.get(room.getLastMsgId());
                    if (Objects.nonNull(message)) {
                        AbstractMsgHandler strategyNoNull = MsgHandlerFactory.getStrategyNoNull(message.getType());
                        resp.setText(lastMsgUidMap.get(message.getFromUid()).getName() + ":" + strategyNoNull.showContactMsg(message));
                    }
                    resp.setUnreadCount(unReadCountMap.getOrDefault(room.getRoomId(), 0));
                    return resp;
                }).sorted(Comparator.comparing(ChatRoomResp::getActiveTime).reversed())
                .collect(Collectors.toList());
    }

    private Map<Long, RoomBaseInfo> getRoomBaseInfoMap(List<Long> roomIds, Long uid) {
        Map<Long, Room> roomMap = roomCache.getBatch(roomIds);
        // 房间根据好友和群组类型分组
        Map<Integer, List<Long>> groupRoomIdMap = roomMap.values().stream().collect(Collectors.groupingBy(Room::getType,
                Collectors.mapping(Room::getId, Collectors.toList())));
        // 获取群组信息
        List<Long> groupRoomId = groupRoomIdMap.get(RoomTypeEnum.GROUP.getType());
        Map<Long, RoomGroup> roomInfoBatch = roomGroupCache.getBatch(groupRoomId);
        // 获取好友信息
        List<Long> friendRoomId = groupRoomIdMap.get(RoomTypeEnum.FRIEND.getType());
        Map<Long, User> friendRoomMap = getFriendRoomMap(friendRoomId, uid);

        return roomMap.values().stream().map(room -> {
            RoomBaseInfo roomBaseInfo = new RoomBaseInfo();
            roomBaseInfo.setRoomId(room.getId());
            roomBaseInfo.setType(room.getType());
            roomBaseInfo.setHotFlag(room.getHotFlag());
            roomBaseInfo.setLastMsgId(room.getLastMsgId());
            roomBaseInfo.setActiveTime(room.getActiveTime());
            if (RoomTypeEnum.of(room.getType()) == RoomTypeEnum.GROUP) {
                RoomGroup roomGroup = roomInfoBatch.get(room.getId());
                roomBaseInfo.setName(roomGroup.getName());
                roomBaseInfo.setAvatar(roomGroup.getAvatar());
            } else if (RoomTypeEnum.of(room.getType()) == RoomTypeEnum.FRIEND) {
                User user = friendRoomMap.get(room.getId());
                roomBaseInfo.setName(user.getName());
                roomBaseInfo.setAvatar(user.getAvatar());
            }
            return roomBaseInfo;
        }).collect(Collectors.toMap(RoomBaseInfo::getRoomId, Function.identity()));
    }

    private Map<Long, User> getFriendRoomMap(List<Long> roomIds, Long uid) {
        if (CollectionUtil.isEmpty(roomIds)) {
            return new HashMap<>();
        }
        Map<Long, RoomFriend> roomFriendMap = roomFriendCache.getBatch(roomIds);
        Set<Long> friendUidSet = ChatAdapter.getFriendUidSet(roomFriendMap.values(), uid);
        Map<Long, User> userBatch = userInfoCache.getBatch(new ArrayList<>(friendUidSet));
        return roomFriendMap.values()
                .stream()
                .collect(Collectors.toMap(RoomFriend::getRoomId, roomFriend -> {
                    Long friendUid = ChatAdapter.getFriendUid(roomFriend, uid);
                    return userBatch.get(friendUid);
                }));
    }


    /**
     * 获取未读数
     */
    private Map<Long, Integer> getUnReadCountMap(Long uid, List<Long> roomIds) {
        if (Objects.isNull(uid)) {
            return new HashMap<>();
        }
        List<Contact> contacts = contactDao.getByRoomIds(roomIds, uid);
        return contacts.parallelStream()
                .map(contact -> Pair.of(contact.getRoomId(), messageDao.getUnReadCount(contact.getRoomId(), contact.getReadTime())))
                .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
    }

    private Double getCursorOrNull(String cursor) {
        return Optional.ofNullable(cursor).map(Double::parseDouble).orElse(null);
    }


}
